/******************************************************************************
 * 
 * Announce: CSharpKit, Basic algorithms, components and definitions.
 *           Copyright (C) ShenYongchen.
 *           All rights reserved.
 *   Author: 申永辰.郑州 (shenyczz@163.com)
 *  WebSite: http://github.com/shenyczz/CSharpKit
 *
 * THIS CODE IS LICENSED UNDER THE MIT LICENSE (MIT).
 * THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF 
 * ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
 * IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
 * PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
 * 
******************************************************************************/
using System;
using CSharpKit.Numerics.LinearAlgebra;
using CSharpKit.Numerics.LinearRegression;

namespace CSharpKit.Numerics.Theories
{
    /// <summary>
    /// 最小二乘曲线拟合 - Least-Squares Curve Fitting Routines
    /// </summary>
    public static class Fit
    {
        /// <summary>
        /// 最小二乘法拟合点集合 points(x,y) 到曲线 y:x -> a+b*x,<para/>
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns>
        /// returning its best fitting parameters as [a, b] array,
        /// where a is the intercept and b the slope.
        /// </returns>
        public static Tuple<double, double> Line(double[] x, double[] y)
        {
            return SimpleRegression.Fit(x, y);
        }


        /// <summary>
        /// Least-Squares fitting the points (x,y) to a line y : x -> a+b*x,
        /// returning a function y' for the best fitting line.
        /// </summary>
        public static Func<double, double> LineFunc(double[] x, double[] y)
        {
            var parameters = SimpleRegression.Fit(x, y);
            double intercept = parameters.Item1, slope = parameters.Item2;
            return z => intercept + slope * z;
        }

        /// <summary>
        /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x,
        /// returning its best fitting parameter b,
        /// where the intercept is zero and b the slope.
        /// </summary>
        public static double LineThroughOrigin(double[] x, double[] y)
        {
            return SimpleRegression.FitThroughOrigin(x, y);
        }

        /// <summary>
        /// Least-Squares fitting the points (x,y) to a line through origin y : x -> b*x,
        /// returning a function y' for the best fitting line.
        /// </summary>
        public static Func<double, double> LineThroughOriginFunc(double[] x, double[] y)
        {
            double slope = SimpleRegression.FitThroughOrigin(x, y);
            return z => slope * z;
        }


        /// <summary>
        /// 幂函数: y:x -> a*exp(r*x) <para/>
        /// Least-Squares fitting the points (x,y) to an exponential y : x -> a*exp(r*x),
        /// returning its best fitting parameters as (a, r) tuple.
        /// </summary>
        public static Tuple<double, double> Exponential(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR)
        {
            // Transformation: y_h := ln(y) ~> y_h : x -> ln(a) + r*x;
            double[] lny = Generator.Map(y, System.Math.Log);
            double[] p = LinearCombination(x, lny, method, t => 1.0, t => t);
            return Tuple.Create(System.Math.Exp(p[0]), p[1]);
        }

        /// <summary>
        /// Least-Squares fitting the points (x,y) to an exponential y : x -> a*exp(r*x),
        /// returning a function y' for the best fitting line.
        /// </summary>
        public static Func<double, double> ExponentialFunc(double[] x, double[] y, DirectRegressionMethod method = DirectRegressionMethod.QR)
        {
            var parameters = Exponential(x, y, method);
            var a = parameters.Item1;
            var r = parameters.Item2;
            return z => a * System.Math.Exp(r * z);
        }

        /// <summary>
        /// 线性组合: y:x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x)
        /// Least-Squares fitting the points (x,y) to an arbitrary linear combination y : x -> p0*f0(x) + p1*f1(x) + ... + pk*fk(x),
        /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array.
        /// </summary>
        public static double[] LinearCombination(double[] x, double[] y, DirectRegressionMethod method, params Func<double, double>[] functions)
        {
            var design = Matrix<double>.Builder.Dense(x.Length, functions.Length, (i, j) => functions[j](x[i]));
            // return MultipleRegression.DirectMethod(design, Vector<double>.Build.Dense(y), method).ToArray();
            throw new NotImplementedException();
        }





        /// <summary>
        /// Least-Squares fitting the points (x,y) to a k-order polynomial y : x -> p0 + p1*x + p2*x^2 + ... + pk*x^k,
        /// returning its best fitting parameters as [p0, p1, p2, ..., pk] array, compatible with Polynomial.Evaluate.
        /// A polynomial with order/degree k has (k+1) coefficients and thus requires at least (k+1) samples.
        /// </summary>
        public static double[] Polynomial(double[] x, double[] y, int order, DirectRegressionMethod method = DirectRegressionMethod.QR)
        {
            var design = Matrix<double>.Builder.Dense(x.Length, order + 1, (i, j) => System.Math.Pow(x[i], j));
            // return MultipleRegression.DirectMethod(design, Vector<double>.Builder.Dense(y), method).ToArray();
            throw new NotImplementedException();
        }

        //}}@@@
    }









}
